package main

import (
	"crypto/tls"
	"encoding/base64"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	ui "github.com/gizak/termui/v3"
	"github.com/gizak/termui/v3/widgets"
	"github.com/pquerna/otp"
	"github.com/pquerna/otp/totp"
	yaml "gopkg.in/yaml.v3"

	"image"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"

	"github.com/makiuchi-d/gozxing"
	"github.com/makiuchi-d/gozxing/qrcode"
)

type User struct {
	Name   string `yaml:"name"`
	Secret string `yaml:"secret"`
}

type Config struct {
	Users    []User `yaml:"users"`
	PageSize int    `yaml:"page_size"`
}

type Page struct {
	PageSize   int
	TotalCount int
	CurPage    int
	TotalPage  int
}

func main() {

	//二维码文件
	qr := flag.String("qrcode", "", "qr code image file")

	//mfa url
	orig := flag.String("orig", "", "otpauth://totp/ url")

	//image url
	image_url := flag.String("url", "", "http://127.0.0.1:5000/static/images/qrcode.png")

	//base64
	bs64 := flag.String("base64", "", "image base64 data...")
	flag.Parse()

	if *bs64 != "" || *image_url != "" || *qr != "" {
		var imgs image.Image
		if *bs64 != "" {
			if strings.Contains(*bs64, ",") {
				*bs64 = strings.Split(*bs64, ",")[1]
			}
			img, _, err := image.Decode(base64.NewDecoder(base64.StdEncoding, strings.NewReader(*bs64)))

			if err != nil {
				fmt.Println(err)
				return
			}
			imgs = img
		}
		//通过图片url下载
		if *image_url != "" {
			client := http.Client{
				Transport: &http.Transport{
					TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
				},
			}
			req, err := http.NewRequest("GET", *image_url, nil)
			if err != nil {
				fmt.Println(err)
				return
			}
			resp, err := client.Do(req)
			if err != nil {
				fmt.Println(err)
				return
			}
			img, _, err := image.Decode(resp.Body)
			if err != nil {
				fmt.Println(err)
				return
			}
			imgs = img
		}
		if *qr != "" {
			fi, err := os.Open(*qr)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
			defer fi.Close()
			img, _, err := image.Decode(fi)
			if err != nil {
				fmt.Println(err)
				return
			}
			imgs = img
		}
		buf, e := gozxing.NewBinaryBitmapFromImage(imgs)
		if e != nil {
			fmt.Println(e)
			return
		}

		qrmatrix, err := qrcode.NewQRCodeReader().Decode(buf, nil)
		if err != nil {
			fmt.Println(err)
			return
		}
		t, err := otp.NewKeyFromURL(qrmatrix.String())
		if err != nil {
			fmt.Println(err)
			return
		}
		f1, _ := url.QueryUnescape(t.String())
		fmt.Println(f1)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		account := []User{User{Name: t.AccountName(), Secret: t.Secret()}}
		data, err := yaml.Marshal(account)
		fmt.Println(string(data))
		return

	}

	//通过url解析mfa
	if *orig != "" {
		t, err := otp.NewKeyFromURL(*orig)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		account := []User{User{Name: t.AccountName(), Secret: t.Secret()}}
		data, err := yaml.Marshal(account)
		fmt.Println(string(data))
		return
	}

	//初始化ui
	if err := ui.Init(); err != nil {
		log.Fatalf("failed to initialize termui: %v", err)
	}
	defer ui.Close()

	//读取配置文件
	data, err := ioutil.ReadFile("./config.yaml")
	if err != nil {
		fmt.Println("config.yaml read failed")
	}

	var config Config
	yaml.Unmarshal(data, &config)

	//分页
	var page Page
	page.TotalCount = len(config.Users)
	page.PageSize = config.PageSize
	page.CurPage = 1
	if page.TotalCount < page.PageSize {
		page.TotalPage = 1
	} else if page.TotalCount == page.PageSize {
		page.TotalPage = 1
	} else if page.TotalCount > page.PageSize {

		m := page.TotalCount % page.PageSize
		if m > 0 {
			page.TotalPage = page.TotalCount/page.PageSize + 1
		} else {
			page.TotalPage = page.TotalCount / page.PageSize
		}
	}

	//布局
	grid := ui.NewGrid()
	termWidth, termHeight := ui.TerminalDimensions()
	grid.SetRect(0, 0, termWidth, termHeight)

	//组件
	p := widgets.NewParagraph()
	p.Block.Title = "time"
	p.Block.TitleStyle.Fg = ui.ColorGreen
	updatep := func() {
		p.Text = time.Now().Format("2006-01-02 15:04:05")
		ui.Render(p)
	}

	p.Text = time.Now().Format("2006-01-02 15:04:05")

	//表格组件
	table := widgets.NewTable()

	var rows [][]string
	rows = append(rows, []string{"User", "Secret", "Code", "Exp"})
	table.Rows = rows

	table.ColumnWidths = []int{int(float64(termWidth) * 0.25), int(float64(termWidth) * 0.55), int(float64(termWidth) * 0.1), int(float64(termWidth) * 0.1)}

	//更新表格
	updatetable := func() {
		var rows [][]string
		rows = append(rows, []string{"User", "Secret", "Code", "Exp"})

		itemdata := config.Users[:]
		if page.PageSize*page.CurPage >= page.TotalCount {
			itemdata = config.Users[(page.CurPage-1)*page.PageSize : page.TotalCount]
		} else if page.PageSize*page.CurPage < page.TotalCount {
			itemdata = config.Users[(page.CurPage-1)*page.PageSize : page.PageSize*page.CurPage]
		}

		for _, item := range itemdata {

			code, err := totp.GenerateCode(item.Secret, time.Now())
			if err != nil {
				fmt.Println(err)
			}

			//计算code过期时间，按30秒间隔计算，其它得也没支持
			secode := time.Now().Second()

			if secode <= 30 {
				secode = 30 - secode
			}
			if secode > 30 {
				secode = 60 - secode
			}
			// fmt.Println(secode)
			rows = append(rows, []string{item.Name, item.Secret, string(code), fmt.Sprintf(" %d ", secode)})
		}
		table.Rows = rows
		ui.Render(table)
	}

	table.TextStyle = ui.NewStyle(ui.ColorWhite)
	// table1.SetRect(100, 100, 60, 10)

	//把组件加到grid中
	grid.Set(ui.NewRow(.14/1, ui.NewCol(1, p)),
		ui.NewRow(.86/1, ui.NewCol(1.0/1, table)),
	)
	ui.Render(grid)

	//事件处理
	uiEvents := ui.PollEvents()
	ticker := time.NewTicker(time.Second).C
	for {
		select {
		case e := <-uiEvents:
			switch e.ID {
			case "q", "<C-c>":
				return

			//调整窗口大小
			case "<Resize>":
				payload := e.Payload.(ui.Resize)
				table.ColumnWidths = []int{int(float64(payload.Width) * 0.25), int(float64(payload.Width) * 0.55), int(float64(payload.Width) * 0.1), int(float64(payload.Width) * 0.1)}
				grid.SetRect(0, 0, payload.Width, payload.Height)
				ui.Clear()

				ui.Render(grid)

				//鼠标，键盘
			case "<Up>", "<MouseWheelUp>":
				if page.CurPage > 1 {
					page.CurPage -= 1
					updatetable()
				}

			case "<Down>", "<MouseWheelDown>":
				if page.CurPage < page.TotalPage {
					page.CurPage += 1
					updatetable()

				}
			}
			//定时更新
		case <-ticker:
			updatep()
			updatetable()
		}
	}
}
